/*******************************************************************************
 * Copyright (c) 2011 Irineu Martins de Lima Moura (imlm2).
 * This file is part of JBeat.
 * 
 *     JBeat is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     JBeat is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 * 
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with JBeat.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Contributors:
 *     Irineu Martins de Lima Moura (imlm2) - initial API and implementation
 ******************************************************************************/
package br.ufpe.cin.imlm2.jbeat.soot.throwableset.propagation;

import java.util.*;

import soot.FastHierarchy;
import soot.Scene;
import soot.SootClass;
import soot.SootMethod;
import soot.jimple.Stmt;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.jimple.toolkits.callgraph.Edge;

import br.ufpe.cin.imlm2.jbeat.soot.common.MethodTagger;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.tags.ThrowableSetTag;
import br.ufpe.cin.imlm2.jbeat.soot.throwableset.tags.TrapedSetTag;
import br.ufpe.cin.imlm2.jbeat.soot.traps.TrapManager;
import br.ufpe.cin.imlm2.util.Naming;

public class PropagatesCalculator extends MethodTagger {

	private boolean withClinits = false;
	private boolean withSynthetic = false;

	public PropagatesCalculator() {
	}

	public PropagatesCalculator(boolean withClinits, boolean withAutoGenerated) {
		this.withClinits = withClinits;
		this.withSynthetic = withAutoGenerated;
	}

	/*
	 * 
	 */
	protected void propagateToAbstractTypes(Iterable<SootClass> classes) {
		for (SootClass sootClass : classes) {
			if (sootClass.isAbstract() || sootClass.isInterface()) {
				List<SootMethod> methods = sootClass.getMethods();
				for (SootMethod sootMethod : methods) {
					if (sootMethod.isAbstract()) {
						FastHierarchy hie = Scene.v().getOrMakeFastHierarchy();
						Set<SootMethod> concreteMethods = hie
								.resolveAbstractDispatch(sootClass, sootMethod);
						for (SootMethod concreteOne : concreteMethods) {
							ThrowableSetTag conctst = (ThrowableSetTag) concreteOne
									.getTag(ThrowableSetTag.TAG_NAME);
							ThrowableSetTag absTst = (ThrowableSetTag) sootMethod
									.getTag(ThrowableSetTag.TAG_NAME);
							absTst.addAllGeneratedExceptions(conctst
									.getThrowableSet());
						}
					}
				}
			}

		}
	}

	private void propagateExceptionsThrough(Collection<SootClass> classes) {
		for (SootClass sootClass : classes) {
			List<SootMethod> allMethods = sootClass.getMethods();
			for (SootMethod sootMethod : allMethods) {
				ThrowableSetTag tst = (ThrowableSetTag) sootMethod
						.getTag(ThrowableSetTag.TAG_NAME);
				this.propagateExceptionsFrom(tst.getThrowableSet(), sootMethod);
			}
		}
	}

	protected void propagateExceptionsFrom(Set<SootClass> exceptions,
			SootMethod src) {
		if (this.shouldPropagateFrom(src)) {
			CallGraph cg = this.options.getCallgraph();
			Iterator<Edge> edges = cg.edgesInto(src);
			Edge next;
			while (edges.hasNext()) {
				next = edges.next();
				SootMethod meth = next.getSrc().method();
				Stmt stm = next.srcStmt();
				if (stm != null) {
					// FIXME This check should *really* be reconsidered.
					if (meth.hasTag(ThrowableSetTag.TAG_NAME)
							&& meth.hasTag(TrapedSetTag.TAG_NAME)) {
						ThrowableSetTag throwableSetTag = (ThrowableSetTag) meth
								.getTag(ThrowableSetTag.TAG_NAME);
						TrapedSetTag trapedSetTag = (TrapedSetTag) meth
								.getTag(TrapedSetTag.TAG_NAME);
						Set<SootClass> throwableSet = throwableSetTag.getThrowableSet();
						// We want to make sure that all possible sources of an
						// exception are considered.
						Set<SootClass> cacheSet = new HashSet<SootClass>();
						for (SootClass sootClass : exceptions) {
							if (!TrapManager.isExceptionCaughtAt(sootClass,
									stm, meth.retrieveActiveBody())) {
								cacheSet.add(sootClass);
							} else {
								trapedSetTag.addTrapedException(sootClass, src);
							}
						}
						throwableSetTag.addAllPropagatedExceptions(cacheSet, src);
						if (!throwableSet.containsAll(exceptions)) {
							this.propagateExceptionsFrom(cacheSet, meth);
						}
					}
				}
			}
		}
	}

	protected boolean shouldPropagateFrom(SootMethod m) {
		if (!m.isConcrete()) {
			return false;
		} else if (!this.withClinits && m.getName().equals("<clinit>")) {
			return false;
		} else if (!this.withSynthetic && Naming.isMethodSynthetic(m.getName())) {
			return false;
		} else if (!this.targetSet.contains(m.getDeclaringClass())) {
			return false;
		}
		return true;
	}

	protected void checkTags(SootMethod meth) {
		if (!meth.hasTag(ThrowableSetTag.TAG_NAME)) {
			meth.addTag(new ThrowableSetTag());
		}
		if (!meth.hasTag(TrapedSetTag.TAG_NAME)) {
			meth.addTag(new TrapedSetTag());
		}
	}

	protected void tag(Collection<SootClass> classes) {
		System.err.println("Exception propagation beginning");
		this.withClinits = this.options.isStatic_initializers();
		this.withSynthetic = this.options.isSynthetic_entities();
		Long startTime = System.currentTimeMillis();
		this.propagateExceptionsThrough(classes);
		this.propagateToAbstractTypes(classes);
		System.err.println("Exception propagation finished in "
				+ (System.currentTimeMillis() - startTime) + "ms");
	}

}
